ISO639 = ['aa', 'ab', 'ae', 'af', 'ags', 'ak', 'am', 'an', 'ang', 'ar', 'as',
		  'av', 'ay', 'az', 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn','bo', 'br',
		  'bs', 'ca', 'ce', 'ch', 'cho', 'co', 'cr', 'cs', 'cu', 'cv','cy', 'da',
		  'de', 'dv', 'dz', 'ee', 'el', 'en', 'eo', 'es', 'et', 'eu','fa', 'ff',
		  'fi', 'fj', 'fo', 'fr', 'fy', 'ga', 'gd', 'gl', 'gn', 'grc','gu', 'gv',
		  'ha', 'he', 'hi', 'ho', 'hr', 'ht', 'hu', 'hy', 'hz', 'ia','id', 'ie',
		  'ig', 'ii', 'ik', 'io', 'is', 'it', 'iu', 'ja-Hani', 'jv','ka', 'kg',
		  'ki', 'kj', 'kk', 'kl', 'km', 'kn', 'ko', 'kr', 'ks', 'ku','kv', 'kw',
		  'ky', 'la', 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv','mg', 'mh',
		  'mi', 'mk', 'ml', 'mn', 'mo', 'mr', 'ms', 'mt', 'my', 'mwl','na', 'nb',
		  'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nrf', 'nv', 'ny','oc', 'oj',
		  'om', 'or', 'os', 'osp', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu','rm', 'rn',
		  'ro', 'ru', 'rw', 'sa', 'sc', 'sco', 'sd', 'se', 'sg', 'sga','sh', 'si',
		  'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv',' sw', 'syc',
		  'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tun',
		  'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'vot', 'wa', 'wlm', 'wo',
		  'xh', 'yi', 'yo', 'za', 'zh', 'zu', 'zun', 'zh-Hans', 'zh-Hant']
from math import floor, pow, log
from werkzeug.exceptions import HTTPException, default_exceptions
from werkzeug.routing import RequestRedirect, MethodNotAllowed, NotFound
from wiki import app
from flask_babel import *
from re import match, compile, sub
from time import strftime
from json import loads, dumps
from bs4 import BeautifulSoup
from functools import wraps, update_wrapper
from random import SystemRandom
from string import ascii_lowercase, digits
from flask import make_response, session, render_template, abort, request, url_for, redirect, flash
from wiki.databases import QueueWikiPostDB, WikiPostDB, db
from urllib.parse import urlparse
from jinja2 import Environment, FileSystemLoader, DictLoader

from os import path

babel = Babel(app)


def get_view_function(url, method='GET'):
    """Match a url and return the view and arguments
    it will be called with, or None if there is no view.
    """
    adapter = app.url_map.bind(request.remote_addr)
    try:
        match = adapter.match(url, method=method)
    except RequestRedirect as error:
        return get_view_function(error.new_url, method)
    except Exception:
        return None
    try:
        return app.view_functions[match[0]]
    except KeyError:
        return None

def allowed_file(filename):
	"""Return the file extension.

	Required keyword arguments:
	filename : name of file
	"""
	return '.' in filename and \
       filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']

def nocache(view):
	@wraps(view)
	def no_cache(*args, **kwargs):
		response = make_response(view(*args, **kwargs))
		response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
		response.headers['Pragma'] = 'no-cache'
		response.headers['X-XSS-Protection'] = '1; mode=block'
		response.headers['X-Content-Type-Options'] = 'nosniff'
		return response
	return update_wrapper(no_cache, view)

def login_required(status=True, privilege='any'):
	def real_decorator(function):
		@wraps(function)
		def wrapper(*args, **kwds):
			if status:
				if privilege == 'any':
					if session.get('logged_in'):
						return function(*args, **kwds)
					else:
						return abort(401)
				elif privilege == 'admin':
					if session.get('logged_in') and session.get('current_user')['privilege'] == privilege:
						return function(*args, **kwds)
					else:
						return abort(401)
				else:
					return abort(404)
			elif not status:
				if not session.get('logged_in'):
					return function(*args, **kwds)
				else:
					return abort(404)
		return wrapper
	return real_decorator

@app.before_request
def lang_adapter():
	"""Adapt the content direction and language before every request."""
	if 'lang' in request.args:
		session['lang'] = request.args.get('lang')
	else:
		if not session.get('lang'):
			session['lang'] = app.config['DEFAULT_LANGUAGE'].lower()
	try:
		assert app.config['LANGUAGES'][[string for string in app.config['LANGUAGES'].keys() \
		 if match(session['lang'].title(), string)][0]] == 'RTL'
	except Exception:
		session['direction'] = 'ltr'
	else:
		session['direction'] = 'rtl'

@app.before_request
def csrf_protect():
	""""Check if the send CSRF token in the POST request equal the one which saved in user session."""
	if request.method == 'POST':
		token = session.pop('_csrf_token', None)
		view_name = get_view_function(request.path)
		if view_name is not None:
			if view_name.__name__ != 'add_post' and view_name.__name__ != 'edit_post':	
				if not token or token != request.form.get('_csrf_token'):
					abort(403)

def generate_csrf_token():
	"""Generate random CSRF token (for security reasons)."""
	if '_csrf_token' not in session:
	    session['_csrf_token'] =\
		 ''.join(SystemRandom().choice(ascii_lowercase + digits) for _ in range(30)) # Generate storng CSRF token randomly 
	return session['_csrf_token']

@babel.localeselector
def get_locale():
	"""Return the appropriate locale."""
	try:
		return session['lang']
	except Exception:
		session['lang'] = app.config['DEFAULT_LANGUAGE']
		return app.config['DEFAULT_LANGUAGE']

def redirect_back():
	"""Redirect user to previous visited page (if there is)."""
	try:
		if request.referrer is not None:
			if app.test_client().get(request.referrer).status_code != 404 and \
							app.test_client().get(request.referrer).status_code != 301:
				return redirect(request.referrer)
			else:
				return redirect('/'+urlparse(request.referrer).path.split('/')[1])
		else:
			return redirect(url_for('wiki.index'))
	except Exception:
		return redirect(url_for('wiki.index'))

@app.errorhandler(Exception)
def handle_error(e):
	code = 404
	if isinstance(e, HTTPException):
		code = e.code
	return render_template('wiki/error.html', code=code), code

for ex in default_exceptions:
    app.register_error_handler(ex, handle_error)

def convert_size(size_bytes):
	"""Return the size in human readable version.

	Required keyword arguments:
	size_bytes : the size in bytes (must be integer)
    """
	try:
		if int(size_bytes) == 0:
			return '0B'
		size_name = ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB')
		size = round(size_bytes / pow(1024, int(floor(log(size_bytes, 1024)))), 2)
	except Exception:
		return 0
	else:
		return '%s %s' % (size, size_name[int(floor(log(size_bytes, 1024)))])

def add_wiki(url, address, content, redirect_url, error_redirect, privilege, lang):
	"""Add a query to the wiki posts database.

	Required keyword arguments:
	url            : the url of posts
	address        : post's title
	content        : the content of article
	redirect_url   : the url to redirect if everything goes alright
	error_redirect : the url to redirect if error raised
	privilege      : the post's creator privilege (admin or user)
	lang           : the post's language
	"""
	query = WikiPostDB.query.filter_by(url=url).all()
	if len(query) >= 1:
		flash('Error')
		return redirect(error_redirect)
	if not address: # if the article address is empty, return error
		flash('Error')
		return redirect(error_redirect)
	if privilege == 'admin':
		query = WikiPostDB.query.filter_by(url=url, default_lang=lang).all()
	elif privilege == 'user':
		query = QueueWikiPostDB.query.filter_by(url=url, default_lang=lang).all()
	else:
		return abort(403)
	soup = BeautifulSoup(content,'html.parser')
	for header in soup.findAll(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']):
		header['id'] = header.text
	if privilege == 'admin':
		newQuery = WikiPostDB(address=address, content=str(soup), 
							  url=url, change_history=dumps([gettext('Created by') + ' <a href="{}">{}</a>'.format( \
							  						  url_for('wiki.user_profile', username=session.get('current_user')['username']), 
							  						  session.get('current_user')['username']) + gettext(' at ') + strftime(gettext('%Y-%m-%d %H-%M-%S'))]),
							  creator_id=session.get('current_user')['id'], default_lang=lang)
	elif privilege == 'user':
		newQuery = QueueWikiPostDB(address=address, content=str(soup), 
								   url=url, change_history=dumps([gettext('Created by') + ' <a href="{}">{}</a>'.format( \
								   						   url_for('wiki.user_profile', username=session.get('current_user')['username']), 
								   						   session.get('current_user')['username']) + gettext(' at ') + strftime(gettext('%Y-%m-%d %H-%M-%S'))]), 
								   creator_id=session.get('current_user')['id'], default_lang=lang, orginal_id='false')
	else:
		return abort(403)			
	db.session.add(newQuery)
	db.session.commit()
	return redirect(redirect_url)

def macro_compiler(string, macros_file):
	"""Compile macro in specific string."""
	macroEnv = Environment(loader=FileSystemLoader(path.dirname(path.abspath(__file__))))
	macroEnv.globals['gettext'] = gettext
	content = macroEnv.get_template(path.join('templates/wiki/', macros_file))
	loader = DictLoader({'template': string})
	contentEnv = Environment(loader=loader)
	for attr in vars(content.module).items():
		if attr[0] != '__name__' or attr[0] != '_body_stream':
			contentEnv.globals[str(attr[0])] = attr[1]
	try:
		return contentEnv.get_template('template').render()
	except Exception:
		return string


def edit_wiki(url, Id, address, content, redirect_url, error_redirect, privilege, lang):	
	"""Edit a query in wiki posts database.

	Required keyword arguments:
	url            : the url of posts
	address        : post's title
	content        : the content of article
	redirect_url   : the url to redirect if everything goes alright
	error_redirect : the url to redirect if error raised
	privilege      : the post's creator privilege (admin or user)
	lang           : the post's language
	"""	
	query = WikiPostDB.query.filter_by(address=address).all()
	if len(query) >= 1:
		for result in query:
			if int(result.Id) != int(Id):
				flash('Error')
				return redirect(error_redirect)
	if not address: # if the article address is empty, return error
		flash('Error')
		return redirect(error_redirect)
	query = WikiPostDB.query.filter_by(Id=Id, default_lang=lang).first_or_404()
	soup = BeautifulSoup(content,'html.parser')
	for header in soup.findAll(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']):
		header['id'] = header.text
	if privilege == 'admin':
		query.content = str(soup)
		query.address = address
		query.change_history = loads(query.change_history)
		query.change_history.append(gettext('Edited by') + ' <a href="{}">{}</a>'.format( \
									url_for('wiki.user_profile', username=session.get('current_user')['username']), 
									session.get('current_user')['username']) +  gettext(' at ') \
									+ strftime(gettext('%Y-%m-%d %H-%M-%S')))
		query.change_history = dumps(query.change_history) 
		query.url = url
		db.session.commit()
	elif privilege == 'user':
		newQuery = QueueWikiPostDB(content=str(soup), url=url, 
			                       address=address, change_history=query.change_history, 
			                       creator_id=session.get('current_user')['id'], default_lang=lang, 
			                       orginal_id=str(query.Id))
		db.session.add(newQuery)
		db.session.commit()
	else:
		return abort(404)
	return redirect(redirect_url)